//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2005-2007
//
// Altitude profile view.
//

# include "StdAfx.h"
# include "Resource.h"
# include "AltProfile.h"
# include "Map.h"
# include "Settings.h"
# include "PrintUnits.h"
# include "fast_floor.h"
# include "Globals.h"

bool IsAltitudeProfileDefined (const rgn_t & _rgn) {
	size_t cAltitudePoints = 0;

	const size_t cPoints = _rgn.elements [0].points.size ();

	if (! _rgn.pExtra)
		return false;

	if (_rgn.IsTrack ()) {
		const trackpoints_t & tpoints = _rgn.elements [0].pExtra->TrackPoints;
		const size_t cExtraPoints = tpoints.size ();
		for (size_t cPoint = 0; cPoint < cExtraPoints; ++ cPoint) {
			const trackpoint_info_t & tpoint = tpoints [cPoint];

			if (tpoint.wFlags & trackpoint_info_t::defAltitude)
				++ cAltitudePoints;
		}
	} else {
		assert (_rgn.IsRoute ());
		const routepoints_t & rpoints = _rgn.pExtra->wtr.route ()->points;
		const size_t cExtraPoints = rpoints.size ();
		for (size_t cPoint = 0; cPoint < cExtraPoints; ++ cPoint) {
			const routepoint_info_t & rpoint = rpoints [cPoint];

			const rgn_t * const pWpt = rpoint.pWaypoint;
			const waypoint_info_t * const pWptExtra = pWpt ? pWpt->pExtra->wtr.wpt () : NULL;
			if (pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defAltitude) != 0)
				++ cAltitudePoints;
		}
	}

	return cAltitudePoints >= 2;
}

const int c_nGraphX0 = 50;
const int c_nGraphX1 = 30;
const int c_nGraphY0 = 40;
const int c_nGraphY1 = 30;
const int c_nZoomStepPow2 = 4;
const int c_nScrollRange = 65000;
const int c_nRulerMarkLen = 5;

const float c_fStdRulerStep [3] = {0.25, 0.5, 1};

class CAltitudeProfile {
  public:
	CAltitudeProfile (const rgn_t * _pRgn) :
		m_pRgn (_pRgn),
		m_hGraph (NULL),
		m_bFocus (false),
		m_pbAltitudeDefined (NULL),
		m_pfAltitude (NULL)
	{
		m_fAltitudeK = 1;
		m_fDistanceK = 1;
		m_fPosX = 0.5;
		m_fPosY = 0.5;

		const points_t & points = _pRgn->elements [0].points;
		const size_t cPoints = points.size ();

		m_pbAltitudeDefined = new bool [cPoints];
		::memset (m_pbAltitudeDefined, 0, cPoints*sizeof (bool));
		m_pfAltitude = new float [cPoints];
		::memset (m_pfAltitude, 0, cPoints*sizeof (float));

		if (_pRgn->IsTrack ()) {
			const trackpoints_t & tpoints = _pRgn->elements [0].pExtra->TrackPoints;
			const size_t cExtraPoints = tpoints.size ();
			for (size_t cPoint = 0; cPoint < cExtraPoints; ++ cPoint) {
				const trackpoint_info_t & tpoint = tpoints [cPoint];

				if (tpoint.wFlags & trackpoint_info_t::defAltitude) {
					m_pbAltitudeDefined [cPoint] = true;
					m_pfAltitude [cPoint] = tpoint.fAltitude;
				}
			}
		} else {
			assert (_pRgn->IsRoute ());
			const routepoints_t & rpoints = _pRgn->pExtra->wtr.route ()->points;
			const size_t cExtraPoints = rpoints.size ();
			for (size_t cPoint = 0; cPoint < cExtraPoints; ++ cPoint) {
				const routepoint_info_t & rpoint = rpoints [cPoint];

				const rgn_t * const pWpt = rpoint.pWaypoint;
				const waypoint_info_t * const pWptExtra = pWpt ? pWpt->pExtra->wtr.wpt () : NULL;
				if (pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defAltitude) != 0) {
					m_pbAltitudeDefined [cPoint] = true;
					m_pfAltitude [cPoint] = pWptExtra->fAltitude;
				}
			}
		}

		m_fMaxAltitude = -1e7;
		m_fMinAltitude = +1e7;
		m_fLength = 0;

		if (cPoints == 0)
			return;

		m_fCosY = ::cos (points [0].y*c_PI/180);

		for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
			if (m_pbAltitudeDefined [cPoint]) {
				const float fAltitude = m_pfAltitude [cPoint];
				if (m_fMaxAltitude < fAltitude)
					m_fMaxAltitude = fAltitude;
				if (m_fMinAltitude > fAltitude)
					m_fMinAltitude = fAltitude;
			}

			if (cPoint + 1 < cPoints) {
				const point_t & p0 = points [cPoint];
				const point_t & p1 = points [cPoint + 1];

				m_fLength += ::hypot ((p0.x - p1.x)*m_fCosY, p0.y - p1.y);
			}
		}
	}
	~CAltitudeProfile () {
		delete [] m_pbAltitudeDefined;
		delete [] m_pfAltitude;
	}

	void Draw (HWND _hWnd, HDC _hDC) const {
		RECT rect;
		::GetClientRect (_hWnd, & rect);
		const int nWidth  = rect.right - rect.left;
		const int nHeight = rect.bottom - rect.top;

		// Clean the widow.
		::FillRect (_hDC, & rect, g_hbrWhite);

		//
		// Draw coordinate axes.
		//

		const int nAltitudeAxisLen = nHeight - c_nGraphY0 - c_nGraphY1;
		const int nDistanceAxisLen = nWidth  - c_nGraphX0 - c_nGraphX1;

		const HBRUSH hbrGraphBg = ::CreateSolidBrush (RGB (230, 255, 240));
		CGdiObjectHolder hbrGraphBg_Holder (hbrGraphBg);

		::SelectObject (_hDC, g_hpenBlackThin);
		::SelectObject (_hDC, hbrGraphBg);
		::SelectObject (_hDC, g_hFont);

		::Rectangle (_hDC, c_nGraphX0, c_nGraphY1, nWidth - c_nGraphX1, nHeight - c_nGraphY0);

		const float fAltitudeRange = m_fMaxAltitude - m_fMinAltitude;
		if (fAltitudeRange < 0)
			return;

		const float fAltitude0 = m_fPosY*fAltitudeRange - .5f*fAltitudeRange/m_fAltitudeK + m_fMinAltitude;
		const float fDistance0 = m_fPosX*m_fLength      - .5f*m_fLength/m_fDistanceK;

		// Altitude ruler.
		char strAltitudeUnit [16];
		const float fAltitudeUnit = 1/GetElevationUnitInM (strAltitudeUnit);

		const int nAltitudeMarkStepOrder = fast_floor (::log10 (fAltitudeRange*fAltitudeUnit/m_fAltitudeK)*3);
		if (nAltitudeMarkStepOrder + 6 >= 0 && fAltitudeRange > 0) {
			assert ((nAltitudeMarkStepOrder + 6) % 3 >= 0);
			const float fAltitudeMarkStep = c_fStdRulerStep [(nAltitudeMarkStepOrder + 6) % 3]*::pow (10.f, fast_floor (nAltitudeMarkStepOrder/3.f));
			const float fAltitudeMark_min = ::ceil (fAltitude0*fAltitudeUnit/fAltitudeMarkStep)*fAltitudeMarkStep;
			const float fAltitudeMark_max = fAltitude0*fAltitudeUnit + fAltitudeRange*fAltitudeUnit/m_fAltitudeK;
			for (float fAltitudeMark = fAltitudeMark_min; fAltitudeMark <= fAltitudeMark_max; fAltitudeMark += fAltitudeMarkStep) {
				const int nMark = nHeight - c_nGraphY0 - fast_floor (nAltitudeAxisLen*(fAltitudeMark - fAltitude0*fAltitudeUnit)/(fAltitudeRange*fAltitudeUnit)*m_fAltitudeK + .5f);

				::MoveToEx (_hDC, c_nGraphX0,                   nMark, NULL);
				::LineTo   (_hDC, c_nGraphX0 - c_nRulerMarkLen, nMark);

				char strMark [32];
				if (fAltitudeMarkStep > 2.5f)
					::sprintf (strMark, "%d %s", fast_floor (fAltitudeMark), strAltitudeUnit);
				else if (fAltitudeMarkStep > 0.25f)
					::sprintf (strMark, "%0.1f %s", fAltitudeMark, strAltitudeUnit);
				else if (fAltitudeMarkStep > 0.025f)
					::sprintf (strMark, "%0.2f %s", fAltitudeMark, strAltitudeUnit);
				else if (fAltitudeMarkStep > 0.0025f)
					::sprintf (strMark, "%0.3f %s", fAltitudeMark, strAltitudeUnit);
				else
					::sprintf (strMark, "%0.4f %s", fAltitudeMark, strAltitudeUnit);

				RECT rectMark = {
					c_nGraphX0 - c_nRulerMarkLen - 2, nMark,
					c_nGraphX0 - c_nRulerMarkLen - 2, nMark
				};
				::DrawText (_hDC, strMark, -1, & rectMark, DT_RIGHT | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_NOCLIP);
			}
		}

		// Distance ruler.
		char strDistanceUnit [16];
		const float fDistanceUnit = 40000./360/GetDistanceUnitInKM (strDistanceUnit);

		const int nDistanceMarkStepOrder = fast_floor (::log10 (m_fLength*fDistanceUnit/m_fDistanceK)*3);
		if (nDistanceMarkStepOrder + 6 >= 0 && m_fLength > 0) {
			assert ((nDistanceMarkStepOrder + 6) % 3 >= 0);
			const float fDistanceMarkStep = c_fStdRulerStep [(nDistanceMarkStepOrder + 6) % 3]*::pow (10.f, fast_floor (nDistanceMarkStepOrder/3.f));
			const float fDistanceMark_min = ::ceil (fDistance0*fDistanceUnit/fDistanceMarkStep)*fDistanceMarkStep;
			const float fDistanceMark_max = fDistance0*fDistanceUnit + m_fLength*fDistanceUnit/m_fDistanceK;
			for (float fDistanceMark = fDistanceMark_min; fDistanceMark <= fDistanceMark_max; fDistanceMark += fDistanceMarkStep) {
				const int nMark = c_nGraphX0 + fast_floor (nDistanceAxisLen*(fDistanceMark - fDistance0*fDistanceUnit)/(m_fLength*fDistanceUnit)*m_fDistanceK + .5f);

				::MoveToEx (_hDC, nMark, nHeight - c_nGraphY0, NULL);
				::LineTo   (_hDC, nMark, nHeight - c_nGraphY0 + c_nRulerMarkLen);

				char strMark [32];
				if (fDistanceMarkStep > 2.5f)
					::sprintf (strMark, "%d %s", fast_floor (fDistanceMark), strDistanceUnit);
				else if (fDistanceMarkStep > 0.25f)
					::sprintf (strMark, "%0.1f %s", fDistanceMark, strDistanceUnit);
				else if (fDistanceMarkStep > 0.025f)
					::sprintf (strMark, "%0.2f %s", fDistanceMark, strDistanceUnit);
				else if (fDistanceMarkStep > 0.0025f)
					::sprintf (strMark, "%0.3f %s", fDistanceMark, strDistanceUnit);
				else
					::sprintf (strMark, "%0.4f %s", fDistanceMark, strDistanceUnit);

				RECT rectMark = {
					nMark, nHeight - c_nGraphY0 + c_nRulerMarkLen + 2,
					nMark, nHeight - c_nGraphY0 + c_nRulerMarkLen + 2,
				};
				::DrawText (_hDC, strMark, -1, & rectMark, DT_TOP | DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_NOCLIP);
			}
		}

		//
		// Prepare graph's vertices.
		//

		const points_t & points = m_pRgn->elements [0].points;
		const size_t cPoints = points.size ();

		POINT * const pts = (POINT *) alloca (sizeof (POINT)*cPoints);
		::memset (pts, 0, sizeof (POINT)*cPoints);
		size_t cPointsToDraw = 0;

		float fDistance = 0;
		size_t cPoint;
		for (cPoint = 0; cPoint < cPoints; ++ cPoint) {
			if (cPoint > 0) {
				const point_t & p0 = points [cPoint - 1];
				const point_t & p1 = points [cPoint];

				fDistance += ::hypot ((p0.x - p1.x)*m_fCosY, p0.y - p1.y);
			}

			if (m_pbAltitudeDefined [cPoint]) {
				const float fAltitude = m_pfAltitude [cPoint];

				const float fX = nDistanceAxisLen*(fDistance - fDistance0)/m_fLength     *m_fDistanceK;
				if (::fabs (fX) >= SHRT_MAX)
					continue;
				const float fY = nAltitudeAxisLen*(fAltitude - fAltitude0)/fAltitudeRange*m_fAltitudeK;
				if (::fabs (fY) >= SHRT_MAX)
					continue;

				assert (cPointsToDraw < cPoints);
				pts [cPointsToDraw].x =           c_nGraphX0 + fast_floor (fX + .5f);
				pts [cPointsToDraw].y = nHeight - c_nGraphY0 - fast_floor (fY + .5f);
				if (pts [cPointsToDraw].x > nWidth) {
					++ cPointsToDraw;
					break;
				}
				++ cPointsToDraw;
			}
		}

		//
		// Draw graph's curve.
		//

		const HRGN hrgnGraph = ::CreateRectRgn (c_nGraphX0, c_nGraphY1, nWidth - c_nGraphX1, nHeight - c_nGraphY0);
		CGdiObjectHolder hrgnGraph_Holder (hrgnGraph);
		::SelectClipRgn (_hDC, hrgnGraph);

		::SelectObject (_hDC, g_hpenBlackThin);
		::Polyline (_hDC, pts, cPointsToDraw);

		// Highlight graph's vertices.
		for (cPoint = 0; cPoint < cPointsToDraw; ++ cPoint) {
			RECT rect = {
				pts [cPoint].x - 1, pts [cPoint].y - 1,
				pts [cPoint].x + 2, pts [cPoint].y + 2
			};
			::FillRect (_hDC, & rect, g_hbrRed);
		}

		::SelectClipRgn (_hDC, NULL);

		//
		// Draw focus status.
		//

		if (m_bFocus) {
			::InflateRect (& rect, -5, -5);
			::DrawFocusRect (_hDC, & rect);
		}
	}

	void SetPosX (float _fPosX, bool _bRedrawGraph = true) {
		m_fPosX = _fPosX;
		UpdateDistanceScroller (_bRedrawGraph);
	}

	void SetPosY (float _fPosY, bool _bRedrawGraph = true) {
		m_fPosY = _fPosY;
		UpdateAltitudeScroller (_bRedrawGraph);
	}

	void SetZoomDistance (int _nZoomDistancePos, bool _bRedrawGraph = true) {
		m_fDistanceK = ::pow (2.f, _nZoomDistancePos/float (c_nZoomStepPow2));
		UpdateDistanceScroller (_bRedrawGraph);
	}

	void SetZoomAltitude (int _nZoomAltitudePos, bool _bRedrawGraph = true) {
		m_fAltitudeK = ::pow (2.f, _nZoomAltitudePos/float (c_nZoomStepPow2));
		UpdateAltitudeScroller (_bRedrawGraph);
	}

	void UpdateDistanceScroller (bool _bRedrawGraph = true) {
		// Update scroller.
		SCROLLINFO sbi;
		sbi.cbSize = sizeof(SCROLLINFO);
		sbi.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL;
		sbi.nMin = 0;
		sbi.nMax = c_nScrollRange - 1;
		sbi.nPage = fast_floor (c_nScrollRange/m_fDistanceK + .5f);
		sbi.nPos  = fast_floor (m_fPosX*c_nScrollRange - 0.5f*c_nScrollRange/m_fDistanceK + .5f);
		::SetScrollInfo (m_hGraph, SB_HORZ, & sbi, TRUE);

		// Update graph.
		if (_bRedrawGraph)
			Update ();
	}

	void UpdateAltitudeScroller (bool _bRedrawGraph = true) {
		// Update scroller.
		SCROLLINFO sbi;
		sbi.cbSize = sizeof(SCROLLINFO);
		sbi.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL;
		sbi.nMin = 0;
		sbi.nMax = c_nScrollRange - 1;
		sbi.nPage = fast_floor (c_nScrollRange/m_fAltitudeK + .5f);
		sbi.nPos = fast_floor ((1 - m_fPosY)*c_nScrollRange - 0.5f*c_nScrollRange/m_fAltitudeK + .5f);
		::SetScrollInfo (m_hGraph, SB_VERT, & sbi, TRUE);

		// Update graph.
		if (_bRedrawGraph)
			Update ();
	}

	void SetFocus (bool _bFocus) {
		m_bFocus = _bFocus;
		Update ();
	}

	void Update () {
		::InvalidateRect (m_hGraph, NULL, FALSE);
		::UpdateWindow (m_hGraph);
	}

	void SetGraphWnd (HWND _hGraph) {assert (m_hGraph == NULL); m_hGraph = _hGraph;}

  private:
	const rgn_t * m_pRgn;

	HWND m_hGraph;
	bool m_bFocus;

	float m_fMaxAltitude;
	float m_fMinAltitude;

	float m_fLength;
	float m_fCosY;

	float m_fPosX;
	float m_fPosY;
	float m_fAltitudeK;
	float m_fDistanceK;

	bool * m_pbAltitudeDefined;
	float * m_pfAltitude;
};

static
LRESULT CALLBACK _AltitudeProfileWndProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_PAINT: {
			PAINTSTRUCT ps;
			const HDC hDC = ::BeginPaint (_hWnd, & ps);

			const CAltitudeProfile * const pThis = reinterpret_cast<const CAltitudeProfile *> (::GetWindowLong (_hWnd, GWL_USERDATA));
			pThis->Draw (_hWnd, hDC);

			::EndPaint (_hWnd, & ps);

			break;
		}

		case WM_CHAR: {
			if (_wParam == '-' || _wParam == '+' || _wParam == '=') {
				const int nChange = _wParam == '-' ? -1 : 1;

				const HWND hZoomDistance = ::GetDlgItem (::GetParent (_hWnd), IDC_ZOOM_DISTANCE);
				const int nZoomDistancePos0 = ::SendMessage (hZoomDistance, TBM_GETPOS, 0, 0) + nChange;
				::SendMessage (hZoomDistance, TBM_SETPOS, TRUE, nZoomDistancePos0);
				const int nZoomDistancePos = ::SendMessage (hZoomDistance, TBM_GETPOS, 0, 0);

				const HWND hZoomAltitude = ::GetDlgItem (::GetParent (_hWnd), IDC_ZOOM_ALTITUDE);
				const int nZoomAltitudePos0 = ::SendMessage (hZoomAltitude, TBM_GETPOS, 0, 0) + nChange;
				::SendMessage (hZoomAltitude, TBM_SETPOS, TRUE, nZoomAltitudePos0);
				const int nZoomAltitudePos = ::SendMessage (hZoomAltitude, TBM_GETPOS, 0, 0);

				CAltitudeProfile * const pThis = reinterpret_cast<CAltitudeProfile *> (::GetWindowLong (_hWnd, GWL_USERDATA));
				pThis->SetZoomDistance (nZoomDistancePos, false);
				pThis->SetZoomAltitude (nZoomAltitudePos, false);
				pThis->Update ();
			}
			break;
		}

		case WM_HSCROLL:
		case WM_VSCROLL: {
			const bool bDistance = _uMsg == WM_HSCROLL;

			CAltitudeProfile * const pThis = reinterpret_cast<CAltitudeProfile *> (::GetWindowLong (_hWnd, GWL_USERDATA));

			SCROLLINFO si;
			si.cbSize = sizeof(SCROLLINFO);
			si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_TRACKPOS;
			::GetScrollInfo (_hWnd, bDistance ? SB_HORZ : SB_VERT, & si);
			int nPos = si.nPos;
			switch (LOWORD (_wParam)) {
				case SB_RIGHT:      nPos = si.nMax - (int) si.nPage + 1; break;
				case SB_LEFT:       nPos = si.nMin; break;
				case SB_LINERIGHT:  nPos += (int) si.nPage/10; break;
				case SB_LINELEFT:   nPos -= (int) si.nPage/10; break;
				case SB_PAGERIGHT:  nPos += (int) si.nPage/2; break;
				case SB_PAGELEFT:   nPos -= (int) si.nPage/2; break;
				case SB_THUMBTRACK: nPos = si.nTrackPos; break;
			}
			if (bDistance)
				pThis->SetPosX (float (nPos + si.nPage/2)/c_nScrollRange);
			else
				pThis->SetPosY (1 - float (nPos + si.nPage/2)/c_nScrollRange);

			break;
		}

		case WM_KEYDOWN: {
			WORD wScrollNotifyH = 0xFFFF;
			WORD wScrollNotifyV = 0xFFFF;
			switch (_wParam) {
				case VK_LEFT:  wScrollNotifyH = SB_LINEUP;    break;
 				case VK_RIGHT: wScrollNotifyH = SB_LINEDOWN;  break;
 				case VK_PRIOR: wScrollNotifyH = SB_PAGELEFT;  break;
 				case VK_NEXT:  wScrollNotifyH = SB_PAGERIGHT; break;
 				case VK_HOME:  wScrollNotifyH = SB_LEFT;      break;
 				case VK_END:   wScrollNotifyH = SB_RIGHT;     break;

				case VK_UP:    wScrollNotifyV = SB_LINEUP;    break;
 				case VK_DOWN:  wScrollNotifyV = SB_LINEDOWN;  break;
			}
 
			if (wScrollNotifyH != -1)
				::SendMessage (_hWnd, WM_HSCROLL, MAKELONG (wScrollNotifyH, 0), 0L);
			if (wScrollNotifyV != -1)
				::SendMessage (_hWnd, WM_VSCROLL, MAKELONG (wScrollNotifyV, 0), 0L);

			break;
		}

		case WM_LBUTTONDOWN:
			::SetFocus (_hWnd);
			break;

		case WM_SETFOCUS: {
			CAltitudeProfile * const pThis = reinterpret_cast<CAltitudeProfile *> (::GetWindowLong (_hWnd, GWL_USERDATA));
			pThis->SetFocus (true);
			break;
		}

		case WM_KILLFOCUS: {
			CAltitudeProfile * const pThis = reinterpret_cast<CAltitudeProfile *> (::GetWindowLong (_hWnd, GWL_USERDATA));
			pThis->SetFocus (false);
			break;
		}

		case WM_GETDLGCODE:
			return DLGC_WANTARROWS | DLGC_WANTCHARS;

		default:
			return ::DefWindowProc (_hWnd, _uMsg, _wParam, _lParam);
	}

	return FALSE;
}

static
INT_PTR CALLBACK _AltitudeProfileProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_INITDIALOG: {
			CAltitudeProfile * const pThis = reinterpret_cast<CAltitudeProfile *> (_lParam);
			::SetWindowLong (_hWnd, GWL_USERDATA, _lParam);

			// Graph.
			const HWND hGraph = ::GetDlgItem (_hWnd, IDC_GRAPH);
			pThis->SetGraphWnd (hGraph);
			::SetWindowLong (hGraph, GWL_USERDATA, _lParam);

			// Scrollers.
			pThis->UpdateDistanceScroller (false);
			pThis->UpdateAltitudeScroller (false);

			::SendMessage (hGraph, SBM_ENABLE_ARROWS, ESB_ENABLE_BOTH, 0);

			// Zoom distance.
			const HWND hZoomDistance = ::GetDlgItem (_hWnd, IDC_ZOOM_DISTANCE);
			::SendMessage (hZoomDistance, TBM_SETRANGE, FALSE, MAKELONG (0, c_nZoomStepPow2*10));
			::SendMessage (hZoomDistance, TBM_SETPAGESIZE, 0, c_nZoomStepPow2);
			::SendMessage (hZoomDistance, TBM_SETPOS, TRUE, 0);

			// Zoom altitude.
			const HWND hZoomAltitude = ::GetDlgItem (_hWnd, IDC_ZOOM_ALTITUDE);
			::SendMessage (hZoomAltitude, TBM_SETRANGE, FALSE, MAKELONG (0, c_nZoomStepPow2*10));
			::SendMessage (hZoomAltitude, TBM_SETPAGESIZE, 0, c_nZoomStepPow2);
			::SendMessage (hZoomAltitude, TBM_SETPOS, TRUE, 0);

			// Restore window position & size.
			RECT rect;
			::GetWindowRect (_hWnd, & rect);
			::SetWindowPos (
				_hWnd, NULL,
				GetSetting ("AltProfile_X", (DWORD) rect.left),
				GetSetting ("AltProfile_Y", (DWORD) rect.top),
				GetSetting ("AltProfile_W", (DWORD) (rect.right - rect.left)),
				GetSetting ("AltProfile_H", (DWORD) (rect.bottom - rect.top)),
				SWP_NOREDRAW | SWP_NOZORDER
			);

			return TRUE;
		}

		case WM_HSCROLL: {
			// On distance zoom changed.
			const HWND hZoomDistance = ::GetDlgItem (_hWnd, IDC_ZOOM_DISTANCE);
			if (hZoomDistance == reinterpret_cast<HWND> (_lParam)) {
				CAltitudeProfile * const pThis = reinterpret_cast<CAltitudeProfile *> (::GetWindowLong (_hWnd, GWL_USERDATA));

				const int nZoomDistancePos = ::SendMessage (hZoomDistance, TBM_GETPOS, 0, 0);
				pThis->SetZoomDistance (nZoomDistancePos);
			}
			break;
		}

		case WM_VSCROLL: {
			// On altitude zoom changed.
			const HWND hZoomAltitude = ::GetDlgItem (_hWnd, IDC_ZOOM_ALTITUDE);
			if (hZoomAltitude == reinterpret_cast<HWND> (_lParam)) {
				CAltitudeProfile * const pThis = reinterpret_cast<CAltitudeProfile *> (::GetWindowLong (_hWnd, GWL_USERDATA));

				const int nZoomAltitudePos = ::SendMessage (hZoomAltitude, TBM_GETPOS, 0, 0);
				pThis->SetZoomAltitude (nZoomAltitudePos);
			}
			break;
		}

		case WM_SIZING:
			return TRUE;

		case WM_SIZE: {
			// Save windows position & size.
			RECT rect;
			::GetWindowRect (_hWnd, & rect);
			SaveSetting ("AltProfile_X", (DWORD) rect.left);
			SaveSetting ("AltProfile_Y", (DWORD) rect.top);
			SaveSetting ("AltProfile_W", (DWORD) (rect.right - rect.left));
			SaveSetting ("AltProfile_H", (DWORD) (rect.bottom - rect.top));

			const int nWidth  = LOWORD (_lParam);
			const int nHeight = HIWORD (_lParam);

			HDWP hDWP = ::BeginDeferWindowPos (3);

			const int nXBorder = ::GetSystemMetrics (SM_CXEDGE);
			const int nYBorder = ::GetSystemMetrics (SM_CYEDGE);

			const int nMarginWidth  = 4*LOWORD (::GetDialogBaseUnits ())/4;
			const int nMarginHeight = 4*HIWORD (::GetDialogBaseUnits ())/8;

			// Graph.
			const HWND hGraph = ::GetDlgItem (_hWnd, IDC_GRAPH);

			RECT rectGraph;
			::GetClientRect (hGraph, & rectGraph);
			::MapWindowPoints (hGraph, _hWnd, reinterpret_cast<POINT *> (& rectGraph), 2);

			const int nGraphWidth  = nWidth  - rectGraph.left - nMarginWidth  + nXBorder*2;
			const int nGraphHeight = nHeight - rectGraph.top  - nMarginHeight + nYBorder*2;

			hDWP = ::DeferWindowPos (hDWP, hGraph, NULL, 0, 0, nGraphWidth, nGraphHeight, SWP_NOMOVE | SWP_NOZORDER);

			// Zoom distance.
			const HWND hZoomDistance = ::GetDlgItem (_hWnd, IDC_ZOOM_DISTANCE);

			RECT rectZoomDistance;
			::GetClientRect (hZoomDistance, & rectZoomDistance);
			::MapWindowPoints (hZoomDistance, _hWnd, reinterpret_cast<POINT *> (& rectZoomDistance), 2);

			hDWP = ::DeferWindowPos (
				hDWP, hZoomDistance, NULL,
				0, 0,
				nGraphWidth,
				rectZoomDistance.bottom - rectZoomDistance.top,
				SWP_NOMOVE | SWP_NOZORDER
			);

			// Zoom altitude.
			const HWND hZoomAltitude = ::GetDlgItem (_hWnd, IDC_ZOOM_ALTITUDE);

			RECT rectZoomAltitude;
			::GetClientRect (hZoomAltitude, & rectZoomAltitude);
			::MapWindowPoints (hZoomAltitude, _hWnd, reinterpret_cast<POINT *> (& rectZoomAltitude), 2);

			hDWP = ::DeferWindowPos (
				hDWP, hZoomAltitude, NULL,
				0, 0,
				rectZoomAltitude.right - rectZoomAltitude.left,
				nGraphHeight,
				SWP_NOMOVE | SWP_NOZORDER
			);

			::EndDeferWindowPos (hDWP);

			break;
		}

		case WM_COMMAND: 
			switch (LOWORD (_wParam)) {
//				case ID_ZOOM_IN:
//				case ID_ZOOM_OUT:
//					break;

				case IDCANCEL:
					::EndDialog (_hWnd, 0);
					break;
			}
			break;
	}

	return FALSE;
}

static bool m_bAltClassDefined = false;

void ShowAltitudeProfile (HWND _hWnd, const rgn_t & _rgn) {
	if (! m_bAltClassDefined) {
		WNDCLASSEX wcex;
		::memset (& wcex, 0, sizeof (wcex));
		wcex.cbSize        = sizeof (WNDCLASSEX);
		wcex.style         = CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc   = (WNDPROC) _AltitudeProfileWndProc;
		wcex.hInstance     = g_hResInst;
		wcex.hCursor       = g_hArrowCursor;
		wcex.lpszClassName = "ALTITUDE_PROFILE_WND";
		wcex.hIconSm       = ::LoadIcon (g_hInst, (LPCTSTR) IDI_MAIN);
		::RegisterClassEx (& wcex);

		m_bAltClassDefined = true;
	}

	CAltitudeProfile ap (& _rgn);
	DlgBox (IDD_ALTITUDE_PROFILE, _hWnd, _AltitudeProfileProc, reinterpret_cast<LPARAM> (& ap));
}